/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.panpf.sketch.sample.ui;

import me.panpf.sketch.Sketch;
import me.panpf.sketch.SketchImageView;
import me.panpf.sketch.datasource.DataSource;
import me.panpf.sketch.decode.ImageAttrs;
import me.panpf.sketch.display.FadeInImageDisplayer;
import me.panpf.sketch.drawable.SketchDrawable;
import me.panpf.sketch.drawable.SketchGifDrawable;
import me.panpf.sketch.drawable.SketchRefBitmap;
import me.panpf.sketch.request.*;
import me.panpf.sketch.sample.AppConfig;
import me.panpf.sketch.sample.ResourceTable;
import me.panpf.sketch.sample.bean.Image;
import me.panpf.sketch.sample.util.ApplyWallpaperAsyncTask;
import me.panpf.sketch.sample.util.FixedThreeLevelScales;
import me.panpf.sketch.sample.util.SaveImageAsyncTask;
import me.panpf.sketch.sample.widget.DialogHolder;
import me.panpf.sketch.sample.widget.SampleImageView;
import me.panpf.sketch.sample.widget.fraction.BaseFraction;
import me.panpf.sketch.sample.widget.page.HintView;
import me.panpf.sketch.sample.widget.page.MappingView;
import me.panpf.sketch.state.MemoryCacheStateImage;
import me.panpf.sketch.uri.FileUriModel;
import me.panpf.sketch.uri.GetDataSourceException;
import me.panpf.sketch.uri.UriModel;
import me.panpf.sketch.util.Log;
import me.panpf.sketch.util.SketchUtils;
import me.panpf.sketch.util.TextUtils;
import me.panpf.sketch.util.Utils;
import me.panpf.sketch.zoom.*;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.IntentParams;
import ohos.agp.colors.RgbPalette;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ComponentState;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.element.Element;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Rect;
import ohos.agp.window.dialog.IDialog;
import ohos.eventhandler.EventHandler;
import org.greenrobot.eventbus.EventBus;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;

import static me.panpf.sketch.sample.ui.ImageFraction.ImageFragmentBuild.PARAM_REQUIRED_STRING_IMAGE_URI;
import static ohos.agp.components.Image.ScaleMode.*;

/**
 * ImageFraction
 */
public class ImageFraction extends BaseFraction {
    SampleImageView imageImageFragmentImage;
    MappingView mappingImageFragment;
    HintView hintImageFragmentHint;
    EventHandler handler;
    private Image image;
    private String loadingImageOptionsKey;
    private Boolean showTools;
    private ClickHelper clickHelper = new ClickHelper();
    private SetWindowBackgroundHelper setWindowBackgroundHelper = new SetWindowBackgroundHelper();
    private GifPlayFollowPageVisibleHelper gifPlayFollowPageVisibleHelper = new GifPlayFollowPageVisibleHelper();
    private String finalShowImageUrl;
    private IntentParams intentParams;
    private DialogHolder dialogHolder;

    ImageFraction(Object parent, EventHandler handler) {
        super(parent);
        this.handler = handler;
        dialogHolder = DialogHolder.getInstance();
    }

    public void setIntentParams(IntentParams intentParams) {
        this.intentParams = intentParams;
    }

    @Override
    public int getLayoutResId() {
        return ResourceTable.Layout_fraction_image;
    }

    @Override
    protected void onStart(Intent intent) {
        super.onStart(intent);
        image = (Image) intentParams.getParam(PARAM_REQUIRED_STRING_IMAGE_URI);
        loadingImageOptionsKey = (String) intentParams.getParam(ImageFragmentBuild.PARAM_REQUIRED_STRING_LOADING_IMAGE_OPTIONS_KEY);
        showTools = (Boolean) intentParams.getParam(ImageFragmentBuild.PARAM_REQUIRED_BOOLEAN_SHOW_TOOLS);
        Boolean showHighDefinitionImage = AppConfig.getBoolean(getFractionAbility(), AppConfig.Key.SHOW_UNSPLASH_RAW_IMAGE);
        finalShowImageUrl = showHighDefinitionImage ? image.getRawQualityUrl() : image.getNormalQualityUrl();
    }

    @Override
    protected void onActive() {
        super.onActive();
        getFractionAbility().getUITaskDispatcher().delayDispatch(new Runnable() {
            @Override
            public void run() {
                clickHelper.onViewCreated();
                imageImageFragmentImage.displayImage(finalShowImageUrl);
                imageImageFragmentImage.setZoomEnabled(false);
            }
        }, 10);
    }

    @Override
    public void initView(Component parent) {
        imageImageFragmentImage = (SampleImageView) parent.findComponentById(ResourceTable.Id_image_imageFragment_image);
        mappingImageFragment = (MappingView) parent.findComponentById(ResourceTable.Id_mapping_imageFragment);
        hintImageFragmentHint = (HintView) parent.findComponentById(ResourceTable.Id_hint_imageFragment_hint);

        mappingImageFragment.getOptions().setLoadingImage(ResourceTable.Media_image_loading);
    }

    @Override
    protected Component onComponentAttached(LayoutScatter scatter, ComponentContainer container, Intent intent) {
        return super.onComponentAttached(scatter, container, intent);
    }

    /**
     * ImageFragmentBuild
     */
    public static class ImageFragmentBuild {
        final static String PARAM_REQUIRED_STRING_IMAGE_URI = "PARAM_REQUIRED_STRING_IMAGE_URI";
        final static String PARAM_REQUIRED_STRING_LOADING_IMAGE_OPTIONS_KEY = "PARAM_REQUIRED_STRING_LOADING_IMAGE_OPTIONS_KEY";
        final static String PARAM_REQUIRED_BOOLEAN_SHOW_TOOLS = "PARAM_REQUIRED_BOOLEAN_SHOW_TOOLS";

        IntentParams intentParams = new IntentParams();
        EventHandler handler;

        public ImageFragmentBuild(Image image, String loadingImageOptionsId, Boolean showTools, EventHandler handler) {
            intentParams.setParam(PARAM_REQUIRED_STRING_IMAGE_URI, image);
            intentParams.setParam(PARAM_REQUIRED_STRING_LOADING_IMAGE_OPTIONS_KEY, loadingImageOptionsId);
            intentParams.setParam(PARAM_REQUIRED_BOOLEAN_SHOW_TOOLS, showTools);
            this.handler = handler;
        }

        /**
         * build
         * @param parent
         * @return ImageFraction
         */
        public ImageFraction build(Object parent) {
            ImageFraction fragment = new ImageFraction(parent, handler);
            fragment.setIntentParams(intentParams);
            return fragment;
        }
    }

    /**
     * PlayImageEvent
     */
    public static final class PlayImageEvent {
    }

    private class ShowHelper {
        void onViewCreated() {
            imageImageFragmentImage.setDisplayListener(new ImageDisplayListener());
            imageImageFragmentImage.setDownloadProgressListener(new ImageDownloadProgressListener());
            initOptions();
            imageImageFragmentImage.displayImage(finalShowImageUrl);
        }

        private void initOptions() {
            Ability ability = getFractionAbility();
            if (ability == null) {
                return;
            }

            imageImageFragmentImage.setPage(SampleImageView.Page.DETAIL);

            DisplayOptions options = imageImageFragmentImage.getOptions();

            // 允许播放 GIF
            options.setDecodeGifImage(true);

            // 有占位图选项信息的话就使用内存缓存占位图但不使用任何显示器，否则就是用渐入显示器
            if (!TextUtils.isEmpty(loadingImageOptionsKey)) {
                UriModel uriModel = UriModel.match(getFractionAbility(), finalShowImageUrl);
                SketchRefBitmap cachedRefBitmap = null;
                String memoryCacheKey = null;
                if (uriModel != null) {
                    memoryCacheKey = SketchUtils.makeRequestKey(image.getNormalQualityUrl(), uriModel, loadingImageOptionsKey);
                    cachedRefBitmap = Sketch.with(getFractionAbility()).getConfiguration().getMemoryCache().get(memoryCacheKey);
                }
                if (cachedRefBitmap != null && memoryCacheKey != null) {
                    options.setLoadingImage(new MemoryCacheStateImage(memoryCacheKey, null));
                } else {
                    options.setDisplayer(new FadeInImageDisplayer());
                }
            } else {
                options.setDisplayer(new FadeInImageDisplayer());
            }
        }


        class ImageDisplayListener implements DisplayListener {
            @Override
            public void onStarted() {
                if (getView() == null) {
                    return;
                }
                hintImageFragmentHint.loading(null);
            }

            @Override
            public void onCompleted(Element drawable, ImageFrom imageFrom, ImageAttrs imageAttrs) {
                if (getView() == null) {
                    return;
                }

                hintImageFragmentHint.hidden();
                setWindowBackgroundHelper.onDisplayCompleted();
                gifPlayFollowPageVisibleHelper.onDisplayCompleted();
            }

            @Override
            public void onError(ErrorCause cause) {
                if (getView() == null) {
                    return;
                }

                hintImageFragmentHint.hint(ResourceTable.Graphic_ic_error, "Image display failed", "Again", new Component.ClickedListener() {
                    @Override
                    public void onClick(Component component) {
                        imageImageFragmentImage.displayImage(finalShowImageUrl);
                    }
                });
            }

            @Override
            public void onCanceled(CancelCause cause) {
                if (getView() == null) {
                    return;
                }

                switch (cause) {
                    case PAUSE_DOWNLOAD:
                        hintImageFragmentHint.hint(ResourceTable.Graphic_ic_error, "Pause to download new image for saving traffic", "I do not care", new Component.ClickedListener() {

                            @Override
                            public void onClick(Component component) {
                                RequestLevel requestLevel = imageImageFragmentImage.getOptions().getRequestLevel();
                                imageImageFragmentImage.getOptions().setRequestLevel(RequestLevel.NET);
                                imageImageFragmentImage.displayImage(finalShowImageUrl);
                                imageImageFragmentImage.getOptions().setRequestLevel(requestLevel);
                            }
                        });
                        break;
                    case PAUSE_LOAD:
                        hintImageFragmentHint.hint(ResourceTable.Graphic_ic_error, "Paused to load new image", "Forced to load", new Component.ClickedListener() {
                            @Override
                            public void onClick(Component component) {
                                RequestLevel requestLevel = imageImageFragmentImage.getOptions().getRequestLevel();
                                imageImageFragmentImage.getOptions().setRequestLevel(RequestLevel.NET);
                                imageImageFragmentImage.displayImage(finalShowImageUrl);
                                imageImageFragmentImage.getOptions().setRequestLevel(requestLevel);
                            }
                        });
                        break;
                }
            }
        }

        class ImageDownloadProgressListener implements DownloadProgressListener {

            @Override
            public void onUpdateDownloadProgress(int totalLength, int completedLength) {
                if (getView() == null) {
                    return;
                }

                hintImageFragmentHint.setProgress(totalLength, completedLength);
            }
        }
    }

    /**
     * ZoomHelper
     */
    private class ZoomHelper {
        /**
         * onViewCreated
         */
        public void onViewCreated() {
            imageImageFragmentImage.setZoomEnabled(AppConfig.getBoolean(imageImageFragmentImage.getContext(), AppConfig.Key.SUPPORT_ZOOM));
            if (AppConfig.getBoolean(imageImageFragmentImage.getContext(), AppConfig.Key.FIXED_THREE_LEVEL_ZOOM_MODE)) {
                imageImageFragmentImage.getZoomer().setZoomScales(new FixedThreeLevelScales());
            } else {
                imageImageFragmentImage.getZoomer().setZoomScales(new AdaptiveTwoLevelScales());
            }

            onReadModeConfigChanged();  // 初始化阅读模式
            onUserVisibleChanged();  // 初始化超大图查看器的暂停状态，这一步很重要
        }

        /**
         * onConfigChanged
         */
        public void onConfigChanged() {
            onViewCreated();
        }

        /**
         * onUserVisibleChanged
         */
        public void onUserVisibleChanged() {
            if (getFractionAbility() == null) {
                return;
            }

            ImageZoomer imageZoomer = imageImageFragmentImage.getZoomer();
            if (AppConfig.getBoolean(getFractionAbility(), AppConfig.Key.PAUSE_BLOCK_DISPLAY_WHEN_PAGE_NOT_VISIBLE)) {
                imageZoomer.getBlockDisplayer().setPause(!getVisibleToUser());  // 不可见的时候暂停超大图查看器，节省内存
            } else if (getVisibleToUser() && imageZoomer.getBlockDisplayer().isPaused()) {
                imageZoomer.getBlockDisplayer().setPause(false); // 因为有 PAUSE_BLOCK_DISPLAY_WHEN_PAGE_NOT_VISIBLE 开关的存在，可能会出现关闭开关后依然处于暂停状态的情况，因此这里恢复一下，加个保险
            }
        }

        /**
         * onReadModeConfigChanged
         */
        public void onReadModeConfigChanged() {
            if (getFractionAbility() == null) {
                return;
            }
            ImageZoomer imageZoomer = imageImageFragmentImage.getZoomer();
            imageZoomer.setReadMode(AppConfig.getBoolean(getFractionAbility(), AppConfig.Key.READ_MODE));
        }
    }

    /**
     * MappingHelper
     */
    private class MappingHelper {
        ZoomMatrixChangedListener zoomMatrixChangedListener = new ZoomMatrixChangedListener();

        /**
         * onViewCreated
         */
        public void onViewCreated() {
            if (!showTools) {
                mappingImageFragment.setVisibility(Component.HIDE);
                return;
            }
            ImageZoomer zoomer = imageImageFragmentImage.getZoomer();
            if (zoomer != null) {
                // MappingView 跟随 Matrix 变化刷新显示区域
                zoomer.addOnMatrixChangeListener(zoomMatrixChangedListener);

                // MappingView 跟随碎片变化刷新碎片区域
                zoomer.getBlockDisplayer().setOnBlockChangedListener(new BlockDisplayer.OnBlockChangedListener() {
                    @Override
                    public void onBlockChanged(BlockDisplayer blockDisplayer) {
                        mappingImageFragment.blockChanged(blockDisplayer);
                    }
                });
                // 点击 MappingView 定位到指定位置
                mappingImageFragment.setOnSingleClickListener((x1, y1) -> {
                    PixmapBuild drawable = imageImageFragmentImage.getPixmapBuild();
                    if (drawable == null) {
                        return false;
                    }
                    if (drawable.getPixelMap().getImageInfo().size.width == 0 || drawable.getPixelMap().getImageInfo().size.height == 0) {
                        return false;
                    }
                    if (mappingImageFragment.getWidth() == 0 || mappingImageFragment.getHeight() == 0) {
                        return false;
                    }
                    float widthScale = (float) ((double) drawable.getPixelMap().getImageInfo().size.width / mappingImageFragment.getWidth());
                    float heightScale = (float) ((double) drawable.getPixelMap().getImageInfo().size.height / mappingImageFragment.getHeight());
                    float realX = x1 * widthScale;
                    float realY = y1 * heightScale;
                    boolean showLocationAnimation = AppConfig.getBoolean(imageImageFragmentImage.getContext(), AppConfig.Key.LOCATION_ANIMATE);
                    location(realX, realY, showLocationAnimation);
                    return true;
                });
            } else {
                mappingImageFragment.setOnSingleClickListener(null);
                mappingImageFragment.update(new Size(0, 0), new Rect());
            }
            mappingImageFragment.getOptions().setDisplayer(new FadeInImageDisplayer());
            mappingImageFragment.getOptions().setMaxSize(400, 400);
            mappingImageFragment.displayImage(finalShowImageUrl);
        }

        /**
         * location
         * @param x1
         * @param y1
         * @param animate
         * @return boolean
         */
        public boolean location(float x1, float y1, boolean animate) {
            imageImageFragmentImage.getZoomer().location(x1, y1, animate);
            return true;
        }


        private class ZoomMatrixChangedListener implements ImageZoomer.OnMatrixChangeListener {
            Rect visibleRect = new Rect();

            @Override
            public void onMatrixChanged(ImageZoomer imageZoomer) {
                imageZoomer.getVisibleRect(visibleRect);
                mappingImageFragment.update(imageZoomer.getDrawableSize(), visibleRect);
            }
        }
    }

    /**
     * SetWindowBackgroundHelper
     */
    private class SetWindowBackgroundHelper {
        private PageBackgApplyCallback pageBackgApplyCallback = null;

        /**
         * onCreate
         * @param ability
         */
        public void onCreate(Ability ability) {
            if (ability instanceof PageBackgApplyCallback) {
                setWindowBackgroundHelper.pageBackgApplyCallback = (PageBackgApplyCallback) ability;
            }
        }

        /**
         * onUserVisibleChanged
         */
        public void onUserVisibleChanged() {
            if (pageBackgApplyCallback != null && getVisibleToUser()) {
                pageBackgApplyCallback.onApplyBackground(finalShowImageUrl);
            }
        }

        /**
         * onDisplayCompleted
         */
        public void onDisplayCompleted() {
            onUserVisibleChanged();
        }
    }

    /**
     * GifPlayFollowPageVisibleHelper
     */
    private class GifPlayFollowPageVisibleHelper {
        /**
         * onUserVisibleChanged
         */
        public void onUserVisibleChanged() {
            Element drawable = imageImageFragmentImage.getDrawable();
            Element lastDrawable = drawable;
            if (lastDrawable != null && lastDrawable instanceof SketchGifDrawable) {
                ((SketchGifDrawable) lastDrawable).followPageVisible(getVisibleToUser(), false);
            }
        }

        /**
         * onDisplayCompleted
         */
        public void onDisplayCompleted() {
            Element drawable = imageImageFragmentImage.getDrawable();
            Element lastDrawable = drawable;
            if (lastDrawable != null && lastDrawable instanceof SketchGifDrawable) {
                ((SketchGifDrawable) lastDrawable).followPageVisible(getVisibleToUser(), true);
            }
        }
    }

    /**
     * ClickHelper
     */
    private class ClickHelper {
        /**
         * onViewCreated
         */
        public void onViewCreated() {
            imageImageFragmentImage.setLongClickable(true);
            imageImageFragmentImage.setLongClickListener(new Component.LongClickedListener() {

                @Override
                public void onLongClicked(Component component) {
                    component.getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                        @Override
                        public void run() {
                            if (component instanceof SketchImageView) {
                                dialogHolder.showInfoDialog((SketchImageView) component);
                                dialogHolder.setAutoPlayCallback(() -> {
                                    handler.sendEvent(1, 2000);
                                });
                            }
                        }
                    });
                }
            });
        }

        /**
         * getImageFile
         * @param imageUri
         * @return File
         */
        public File getImageFile(String imageUri) {
            if (getFractionAbility() == null) {
                return null;
            }
            if (TextUtils.isEmpty(imageUri)) {
                return null;
            }

            UriModel uriModel = UriModel.match(getFractionAbility(), imageUri);
            if (uriModel == null) {
                Utils.toast(getFractionAbility(), "Unknown format uri:" + imageUri);
                return null;
            }

            DataSource dataSource;
            try {
                dataSource = uriModel.getDataSource(getFractionAbility(), imageUri, null);
            } catch (GetDataSourceException ex) {
                ex.printStackTrace();
                Utils.toast(getFractionAbility(), "The Image is not ready yet");
                return null;
            }

            try {
                return dataSource.getFile(getFractionAbility().getExternalCacheDir(), null);
            } catch (IOException ex) {
                ex.printStackTrace();
                return null;
            }
        }

        /**
         * share
         */
        public void share() {
            if (getFractionAbility() == null) {
                return;
            }

            Element drawable = imageImageFragmentImage.getDrawable();
            String imageUri = (drawable instanceof SketchDrawable) ? ((SketchDrawable) drawable).getUri() : null;
            if (TextUtils.isEmpty(imageUri)) {
                Utils.toast(getFractionAbility(), "Please wait later");
                return;
            }

            File imageFile = getImageFile(imageUri);
            if (imageFile == null) {
                Utils.toast(getFractionAbility(), "The Image is not ready yet");
                return;
            }
        }

        /**
         * setWallpaper
         */
        public void setWallpaper() {
            if (getFractionAbility() == null) {
                return;
            }

            Element drawable = imageImageFragmentImage.getDrawable();
            String imageUri = (drawable instanceof SketchDrawable) ? ((SketchDrawable) drawable).getUri() :
                    null;
            if (TextUtils.isEmpty(imageUri)) {
                Utils.toast(getFractionAbility(), "The Image is not ready yet");
                return;
            }

            File imageFile = getImageFile(imageUri);
            if (imageFile == null) {
                Utils.toast(getFractionAbility(), "The Image is not ready yet");
                return;
            }
            new ApplyWallpaperAsyncTask(getFractionAbility(), imageFile).execute();
        }

        /**
         * play
         */
        public void play() {
            EventBus.getDefault().post(new PlayImageEvent());
            if (handler != null) {
                handler.sendEvent(1, 2000);
            }
        }

        /**
         * save
         */
        public void save() {
            if (getFractionAbility() == null) {
                return;
            }
            Element drawable = imageImageFragmentImage.getDrawable();
            String imageUri = (drawable instanceof SketchDrawable) ? ((SketchDrawable) drawable).getUri() : null;
            if (TextUtils.isEmpty(imageUri)) {
                Utils.toast(getFractionAbility(), "Please wait later");
                return;
            }

            UriModel uriModel = UriModel.match(getFractionAbility(), imageUri);
            if (uriModel == null) {
                Utils.toast(getFractionAbility(), "Unknown format uri: " + imageUri);
                return;
            }

            if (uriModel instanceof FileUriModel) {
                Utils.toast(getFractionAbility(), "This image is the local no need to save");
                return;
            }

            DataSource dataSource;
            try {
                dataSource = uriModel.getDataSource(getFractionAbility(), imageUri, null);
            } catch (GetDataSourceException e) {
                e.printStackTrace();
                Utils.toast(getFractionAbility(), "The Image is not ready yet");
                return;
            }

            new SaveImageAsyncTask(getFractionAbility(), dataSource, imageUri).execute("");
        }

        public String parseFileType(String fileName) {
            int lastIndexOf = fileName.lastIndexOf("");
            if (lastIndexOf < 0) {
                return null;
            }
            String fileType = fileName.substring(lastIndexOf + 1);
            if ("".equals(fileType.trim())) {
                return null;
            }
            return fileType;
        }

        class MenuItem {
            String title;
            IDialog.ClickedListener clickListener;

            public MenuItem(String title, IDialog.ClickedListener clickListener) {
                this.title = title;
                this.clickListener = clickListener;
            }
        }


    }

}